﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection;

namespace AnaxibiaStudios.Smug.Internal
{
  public enum PreexistingType
  {
    New = 0,
    Conflicting,
    Exact,
    Undefined
  }

  public class MemberCollection : IEnumerable<MemberInfoEx>
  {
    private List<MemberInfoEx> _metas;
    public SourceBuilder Builder { get; protected set; }

    // coupled properties, given for convenience
    public IEnumerable<MethodInfoEx> Methods { get { return ConvenienceEnumerable<MethodInfoEx>(); } }
    public IEnumerable<PropertyInfoEx> Properties { get { return ConvenienceEnumerable<PropertyInfoEx>(); } }
    public IEnumerable<EventInfoEx> Events { get { return ConvenienceEnumerable<EventInfoEx>(); } }
    public IEnumerable<ConstructorInfoEx> Constructors { get { return ConvenienceEnumerable<ConstructorInfoEx>(); } }

    private IEnumerable<TEx> ConvenienceEnumerable<TEx>() where TEx : MemberInfoEx
    {
      return _metas
        .Where(m => !m.IsNull)
        .Where(m => m is TEx)
        .Select(m => m as TEx);
    }

    public MemberCollection(SourceBuilder builder)
    {
      _metas = new List<MemberInfoEx>();
      Builder = builder;
    }

    public bool Add(MemberInfo member)
    {
      var ex = MemberInfoEx.Wrap(this, member);
      PreexistingType hit = Hit(ex);

      // if this exact same member has been added before, ignore it
      if (_metas.Select(next => next.MemberInfo).Contains(member))
        return false;

      if (hit == PreexistingType.New)
      {
        // inclusion ready
      }
      else if (hit == PreexistingType.Conflicting)
      {
        ex.MakeExplicit(); //upgrade to explicit
      }
      else if (hit == PreexistingType.Exact)
      {
        if (ex.DeclaringType.IsInterface) // good, we can declare it
        {
          ex.MakeExplicit(); //upgrade to explicit
        }
        else // this should not happen unless iterating over parent, non interface types
        {
          return false;
        }
      }

      _metas.Add(ex);

      return true;
    }

    public PreexistingType Hit(MemberInfoEx member)
    {
      // null and private cases are not serialized/seen via inheritance - so safe to mark as new
      if (member.IsNull || member.IsPrivate)
        return PreexistingType.New;

      // explicit members are unique
      if (member.IsExplicit)
        return PreexistingType.New;

      bool conflicting = false;
      foreach (var ex in _metas.Where(ex => !ex.IsExplicit))
      {
        var hit = member.Hit(ex);

        switch (hit)
        {
          case PreexistingType.Exact: return hit;
          case PreexistingType.Conflicting: conflicting = true; break;
        }
      }

      if (conflicting) return PreexistingType.Conflicting;
      return PreexistingType.New;
    }

    public int Count { get { return _metas.Count; } }

    public MemberInfoEx this[string name]
    {
      get
      {
        return this[name, null];
      }
    }

    public MemberInfoEx this[string name, bool? exp]
    {
      get
      {
        var matches = _metas.Where(meta => meta.Name == name);

        return exp.HasValue ?
          matches.FirstOrDefault(meta => exp.Value ^ !meta.IsExplicit)
        :
          matches.FirstOrDefault();
      }
    }

    public IEnumerator<MemberInfoEx> GetEnumerator()
    {
      return _metas.GetEnumerator();
    }

    System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
    {
      return _metas.GetEnumerator();
    }

    public virtual string BuildSource()
    {
      return string.Join("",
        _metas
          .Where(m => m.IsValidForDerivedClass)
          .Select(m => m.BuildSource()));
    }

    public bool Contains(Func<MemberInfoEx, bool> match)
    {
      return _metas.FirstOrDefault(match) != null;
    }
  }
}
